﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using log4net;

namespace JiraRest
{
    /// <summary>
    /// Wrapper for the JIRA issue REST API, encapsulates an issue whether it's a project link or autobug.
    /// Turns out that we create JiraBugs in all circumstances, so this may as well be abstract.
    /// We just have to avoid using JiraBug's abilities on things that aren't autobugs.
    /// </summary>
    public abstract class JiraIssue
    {
        private static readonly ILog s_Log = LogManager.GetLogger("JiraResr.JiraIssue");

        private readonly string m_Key;
        private readonly JiraConnection m_Connection;

        private dynamic m_CompleteIssue;

        private Dictionary<string, object> m_FieldChanges = new Dictionary<string, object>();

        public JiraIssue(string key, JiraConnection connection)
        {
            m_Key = key;
            m_Connection = connection;
        }

        public string Key
        {
            get { return m_Key; }
        }

        public string Summary
        {
            get
            {
                return GetIssueFields().summary;
            }
        }

        private dynamic GetIssueFields()
        {
            return GetCompleteIssue().fields;
        }

        private dynamic GetCompleteIssue()
        {
            if (m_CompleteIssue == null)
            {
                string issueJson = m_Connection.Api("issue/" + m_Key + string.Format("?expand=names"));
                m_CompleteIssue = DynamicJsonConverter.Deserialize(issueJson);
            }

            return m_CompleteIssue;
        }

        public string Description
        {
            get { return GetIssueFields().description; }
            set
            {
                SetField(value, "description");
            }
        }

        public string Project
        {
            get { return GetIssueFields().project.name; }
        }

        public DateTime Updated
        {
            get { return DateTime.Parse(GetIssueFields().updated); }
        }

        /// <summary>
        /// This is different from the indexer because it accepts an object rather than a string
        /// </summary>
        public void SetCustomFieldValue(string customFieldName, object value)
        {
            SetField(value, GetCustomFieldId(customFieldName));
        }

        public string this[string customFieldName]
        {
            get
            {
                return (string)GetCustomFieldValue(customFieldName);
            }
            set
            {
                switch (value)
                {
                    case "":
                        throw new Exception("Cannot set custom free text field to the empty string in JIRA; this deletes the field (by design)");
                    case "-1":
                        throw new Exception("Cannot set custom free text field to -1 in JIRA; this deletes the field. See http://jira.atlassian.com/browse/JRA-19240");
                }

                SetField(value, GetCustomFieldId(customFieldName));
            }
        }

        public object GetCustomFieldValue(string customFieldName)
        {
            dynamic issue = GetCompleteIssue();
            Dictionary<string, object> names = issue.names.Dictionary;
            Dictionary<string, string> nameIds = names.ToDictionary(pair => pair.Value as string, pair => pair.Key);
            string customFieldId = nameIds[customFieldName];
            return issue.fields[customFieldId];
        }

        private string GetCustomFieldId(string customFieldName)
        {
            dynamic issue = GetCompleteIssue();
            Dictionary<string, object> names = issue.names.Dictionary;
            Dictionary<string, string> nameIds = names.ToDictionary(pair => pair.Value as string, pair => pair.Key);
            string customFieldId = nameIds[customFieldName];
            return customFieldId;
        }

        public Dictionary<string, object> DictionaryRepresentation
        {
            get { return GetCompleteIssue().Dictionary; }
        }

        public string Status
        {
            get { return GetIssueFields().status.name; }
        }

        public string Resolution
        {
            get { return GetIssueFields().resolution.name; }
        }

        public DateTime ResolvedDateUtc
        {
            get { return DateTime.Parse(GetIssueFields().resolutiondate); }
        }

        private void SetField(object value, string fieldName)
        {
            // Put the change into the queue ready for the commit later
            m_FieldChanges[fieldName] = new[] {new {set = value}};
        }

        public void AddComment(string body, bool isJson)
        {
            // We could potentially add multiple comments in a single commit
            object currentComments;
            if (!m_FieldChanges.TryGetValue("comment", out currentComments))
            {
                currentComments = new ArrayList();
                m_FieldChanges["comment"] = currentComments;
            }

            ArrayList currentCommentsList = (ArrayList) currentComments;
            currentCommentsList.Add(new
                                        {
                                            add = new
                                                      {
                                                          body = JiraUtils.Clean(body, isJson)
                                                      }
                                        });

            s_Log.InfoFormat("Commented on {0}", Key);
        }

        public void CommitChanges()
        {
            object updator = new {update = m_FieldChanges};
            string jsonUpdator = DynamicJsonConverter.Serialize(updator);
            m_Connection.PutApi("issue/" + m_Key, jsonUpdator);
            m_FieldChanges.Clear();
        }
    }
}
